﻿using System;
using System.Collections.Generic;
using System.Text;

namespace LoRel{
    public struct Pair<T> {
        public readonly T Left;
        public readonly T Right;

        public Pair( T left, T right ) {
            Left = left;
            Right = right;
        }
    }

    public class CombinedKeySchema {
        private readonly List< ColumnReference > m_Columns;
        private readonly List< Key > m_EmptyKeys;
        private readonly List< Pair< int > > m_Pairs;
        private readonly int m_Length;

        public CombinedKeySchema( List< ColumnReference > columns, List<Key> empty_keys  ) {
            m_Columns = columns;
            m_EmptyKeys = empty_keys;
            m_Pairs = new List<Pair<int>>();
            
            for( int i=0; i < m_EmptyKeys.Count ; i++ ) {
                m_Pairs.Add(new Pair<int>(m_Length, m_Length + m_EmptyKeys[i].Length));
                m_Length += m_EmptyKeys[i].Length;
            }
            if( m_Length != columns.Count ) throw new ArgumentException("Column count do not match total key length");
        }
        
        public int KeyCount { get { return m_EmptyKeys.Count; } }
        public int Length{ get { return m_Length; }}
        public ColumnReference GetColumn(int column_index) { return m_Columns[ column_index ]; }
        public void GetBounds( int key_index, out Pair<int> bounds  ) { bounds = m_Pairs[ key_index ]; }

        public IComparable[] CreateEmpty() {
            return new IComparable[Length];
        }
    }

    public partial class Key : IComparable, IComparable<Key> {
        private readonly NullReport m_NullReport;
        private readonly IComparable[] m_Values;

        public Key( IList<Key> combined_key ) {
            // TODO: this is pretty inefficient, and can be dramatically improved.
            IComparable[] values = null;
            NullReport null_report = NullReport.AllPopulated;

            for( int i=0 ; i < combined_key.Count-1 ; i++ ) {
                values = combined_key[ i ].CreateCombined( combined_key[ i + 1 ] );
                null_report = combined_key[i].CombinedNulls( combined_key[i + 1] );
            }

            m_NullReport = null_report;
            m_Values = values;
        }

        public Key( NullReport null_report, params IComparable[] values ) {
            m_NullReport = null_report;
            m_Values = values;
        }

        public NullReport NullReport { get { return m_NullReport; } }

        public int Length { get { return m_Values.Length; } }

        #region STATIC

        public static bool operator ==(Key lhs, Object rhs)
        {
            Key rhs_key = rhs as Key;
            return lhs == rhs_key;
        }

        public static bool operator !=(Key lhs, object rhs) { return !(lhs == rhs); }

        public static bool operator ==(Key lhs, Key rhs)
        {
            if (ReferenceEquals(lhs, null))
            {
                if (ReferenceEquals(rhs, null)) return true;
                return false;
            }
            return lhs.Equals(rhs);
        }

        public static bool operator !=(Key lhs, Key rhs) { return !(lhs == rhs); }
        
        #endregion

        #region Implementation of IComparable

        public int CompareTo(object obj) { Key rhs = obj as Key;
            return CompareTo( rhs );
        }

        public int CompareTo( Key other) {

            if( ReferenceEquals( other, null ) ) {
                throw new NotImplementedException("Unsure of what this means");
            }

            if( m_Values.Length != other.m_Values.Length ) {
                throw new NotImplementedException("Unsure of what this means");
            }

            int compare = 0;
            for( int i=0 ; i < m_Values.Length ; i++ ) {
                
                if( ReferenceEquals( m_Values[i], null ) ) {
                    if (ReferenceEquals(other.m_Values[i], null)) continue;
                    else return -1;
                }
                compare = m_Values[ i ].CompareTo( other.m_Values[ i ] );
                if ( compare != 0 ) return compare;
            }
            return compare;
        }

        #endregion

        #region Equality and HashCode

        private bool Equals( Object[] rhs ) {
            if (rhs.Length != m_Values.Length) return false;

            for( int i=0 ; i < m_Values.Length ; i++ ) {
                if( m_Values[i] == null ) {
                    if (rhs[i] != null) return false;
                }else if (m_Values[i].Equals(rhs[i]) == false) return false;
            }
            return true;
        }

        public bool Equals( Key other ) {
            if( ReferenceEquals( null, other ) ) return false;
            if( ReferenceEquals( this, other ) ) return true;
            return Equals( other.m_Values );
        }

        public override bool Equals( object obj ) {
            if( ReferenceEquals( null, obj ) ) return false;
            if( ReferenceEquals( this, obj ) ) return true;
            
            //if( obj.GetType() != typeof( Key ) ) return false;
            if (obj is Key == false) return false;
            return Equals( ( Key ) obj );
        }

        public override int GetHashCode()
        {
            unchecked {
                int result = 0;
                for( int i=0 ; i < m_Values.Length ; i++ ) {
                    result = ( result * 397 ) ^ 
                        ( m_Values[ i ] != null ? m_Values[ i ].GetHashCode() : 0 );
                }
                return result;
            }
        }

        
        #endregion

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Key{");
            for (int i = 0; i < m_Values.Length; i++)
            {
                //sb.Append( m_m_Schema[i].Text)
                //    .Append('=')
                //    .Append(m_Values[i]);
                sb.Append( m_Values[ i ] );
                if (i + 1 < m_Values.Length)
                    sb.Append(", ");
            }
            sb.Append("}");
            return sb.ToString();
        }
        public IComparable[] CreateCombined( Key rhs ) {
            IComparable[] result_set = new IComparable[ m_Values.Length + rhs.m_Values.Length ];
            int i = 0;
            for (; i < m_Values.Length; i++) { result_set[i] = m_Values[i]; }
            for (; i < rhs.m_Values.Length + m_Values.Length; i++) { result_set[i] = rhs.m_Values[i - m_Values.Length]; }
            return result_set;
        }

        public NullReport CombinedNulls( Key rhs ) {
            switch( NullReport ) {
                case NullReport.AllPopulated:
                    switch( rhs.NullReport ) {
                        case NullReport.AllPopulated:
                            return NullReport.AllPopulated;
                        case NullReport.SomeNull:
                        case NullReport.AllNull:
                            return NullReport.SomeNull;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                case NullReport.SomeNull: return NullReport.SomeNull;
                case NullReport.AllNull:
                    switch( rhs.NullReport ) {
                        case NullReport.AllPopulated:
                        case NullReport.SomeNull:
                            return NullReport.SomeNull;
                        case NullReport.AllNull:
                            return NullReport.AllNull;
                        default:
                            throw new ArgumentOutOfRangeException();
                    }
                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        public void CopyTo( int offset, IComparable[] destination_set, int target_offset, int length ) {
            Array.Copy( m_Values, offset, destination_set, target_offset, length );
        }
    }
}
